 package GameLogic;

import Animation.CTDDamageTC;
import Animation.CreepWalk;
import Exceptions.InvalidArgument;
import GameElements.*;
import IO.Path;
import IO.Waypoint;
import Initialize.InitCreep;
import Initialize.InitTower;
import MapLogic.CTowerDefenseMapLogic;
import Properties.props;
import java.util.ArrayList;

public class CTDGameLogic {
	private int gameTime; // in Sekunden
	private int cycletime;
	private int points;
	private int maxX, maxY;
	private ArrayList<CTDCreep> creeps = new ArrayList<CTDCreep>();
	private ArrayList<CTDCreep> deadcreeps = new ArrayList<CTDCreep>();
	private ArrayList<CTowerDefenseElement> tower = new ArrayList<CTowerDefenseElement>();
	private ArrayList<CTowerDefenseElement> buildtower = new ArrayList<CTowerDefenseElement>();
	private CTDDamageTC animationofdamage = new CTDDamageTC();
	private double gold;
	private int uniqueIDCounter = 0;
	private props constants = new props();
	private ArrayList<CTDWave> waves = new ArrayList<CTDWave>();
	private CTowerDefenseMapLogic inMap;

	/**
	 * @return the gold
	 */
	public double getGold() {
		return gold;
	}

	/**
	 * @param gold the gold to set
	 */
	public void setGold(double gold) {
		this.gold = gold;
	}

	
	public int getMaxX() {
		return maxX;
	}

	public void setMaxX(int maxX) {
		this.maxX = maxX;
	}

	public int getMaxY() {
		return maxY;
	}

	public void setMaxY(int maxY) {
		this.maxY = maxY;
	}
	
	public int getPoints(){
		return points;
	}
	
	public void LoadLevelByMapElements(char[][] mapElements) {
		if(mapElements == null) return;
		char[][] mymap = new char[mapElements[0].length][mapElements.length];
		for(int i = 0;i<mapElements[0].length;i++)
			for(int j = 0;j<mapElements.length;j++)
			{
				mymap[i][j] = mapElements[j][i];
			}
		mapElements = mymap.clone();
		inMap = new CTowerDefenseMapLogic();		
		inMap.setMapElements(mapElements.clone());	
		
		/*for(int i =0;i<mymap[0].length;i++)
		{
			for(int j = 0;j<mymap.length;j++)
				System.out.print(mymap[j][i]);
			System.out.print('\n');
		}*/
		char[][] test = inMap.getMapElements();
		for(int i = 0; i<mapElements.length;i++)
			for(int j =0; j<mapElements[0].length;j++)
				if(mapElements[i][j] == 't')
					tower.add(CreateDefaultTower(i, j));
					
	}
	
	public CTDGameLogic(ArrayList<Path> _ctdwp) throws InvalidArgument {
		inMap = new CTowerDefenseMapLogic(25, 25);
		int maxWaves = 20;
		int startWave = 4000;
		gold = props.startGold;
		points = 1000;// constants.startLifes;
		inMap.setWayPath(_ctdwp);
		for (int i = 0; i < inMap.getNbOfWayPaths(); i++) {
			waves.add(new CTDWave());
			waves.get(i).setWaypathID(i);
			waves.get(i).setAktWave(0);
			waves.get(i).updateNext();
			waves.get(i).setMode(0);
			waves.get(i).setWaves(maxWaves);
			waves.get(i).setNextCreepTime(startWave);
			waves.get(i).checkForCreep();
		}
	}

	/**
	 * Default constructor. Use this Constructor in combination with Options() and InitWayPaths() and InitMapElements()
	 */
	public CTDGameLogic() {

	}

	/**
	 * Use this funtion with and only with no Parameter Contructor.
	 * 
	 * @param maxWaves
	 * @param waveStartsInMilSeconds
	 * @param startGold
	 * @param mode
	 *            : 0: Easy, 1: Normal, 2: Hard
	 */
	public void Options(int maxWaves, int waveStartsInMilSeconds,
			int startGold, int mode) {
		points = props.startPoints;
		for (int i = 0; i < waves.size(); i++) {
			waves.get(i).setWaves(maxWaves);
			waves.get(i).setNextCreepTime(waveStartsInMilSeconds);
			gold = startGold;
			waves.get(i).setMode(mode);
		}
	}

	/**
	 * Use this function with and only with no parameter Constructor.
	 * 
	 * @param _ctdwp All Paths of Creeps
	 */
	public void InitWayPaths(ArrayList<Path> _ctdwp) {
		inMap.setWayPath(_ctdwp);
		for (int i = 0; i < inMap.getNbOfWayPaths(); i++) {
			waves.add(new CTDWave());
			waves.get(i).setWaypathID(i);
			waves.get(i).setAktWave(0);
			waves.get(i).updateNext();
			waves.get(i).checkForCreep();
		}
	}
	
	/**
	 * Fills the char Array containing intern representation of GameBoard 
	 * @param _mapelements char Array containing intern representation of GameBoard
	 */
	public void InitMapElements(char[][] _mapelements){
		inMap.setMapElements(_mapelements);
	}

	public ArrayList<CTowerDefenseElement> getTower() {
		return tower;
	}

	public void setTower(ArrayList<CTowerDefenseElement> tower) {
		this.tower = tower;
	}

	public ArrayList<CTDCreep> getCreeps() {
		return creeps;
	}

	public void setCreeps(ArrayList<CTDCreep> creeps) {
		this.creeps = creeps;
	}

	public void UpdateWave() throws InvalidArgument {
		for (int i = 0; i < inMap.getNbOfWayPaths(); i++) {
			if (waves.get(i).checkCreep()) {
				InitCreep ic = new InitCreep();
				Waypoint _wp = inMap.getWayPathByID(i).getWayPoint(0);
				CTDCreep _creep = InitCreep.CreateCreep(waves.get(i).getAktWave(),
						uniqueIDCounter, _wp.getX(), _wp.getY(), i);
				uniqueIDCounter += 1;
				creeps.add(_creep);
			}
		}
	}

	public boolean SellTower(int id) throws InvalidArgument {
		for (int i = 0; i < tower.size(); i++)
			if (tower.get(i).getUniqueID() == id) {
				int money = tower.get(i).getCost();
				int _gold = money * 100 / props.resellPercentage;
				gold += _gold;
				tower.remove(i);
				Synchronize();
				return true;
			}
		return false;
	}

	public boolean UpgradeTower(int id) throws InvalidArgument {
		for (int i = 0; i < tower.size(); i++)
			if (tower.get(i).getUniqueID() == id) {
				if (CheckGold(((CTDTower) tower.get(i)).getUpgradeCost()) == true) {
					((CTDTower) tower.get(i)).Upgrade();
					gold -= ((CTDTower) tower.get(i)).getUpgradeCost();
					Synchronize();
					return true;
				} else
					return false;

			}
		return false;
	}

	private void Synchronize() {
		
		//inMap.flushAllTower();
		inMap.setTowerOnMap(tower);

	}

	private boolean CheckGold(int upgradeCost) {
		double _gold = gold - upgradeCost;
		if (_gold < 0)
			return false;
		return true;
	}

	public int BuyTower(int x, int y, int buildtowerID) throws InvalidArgument {
		char[][] test = inMap.getMapElements().clone();
		if(buildtower.isEmpty())
			buildtower = InitTower.CreateBuildTower();
		if (CheckGold(buildtower.get(buildtowerID).getCost()) == true) {
			if (inMap.getMapElementValue(x, y) != '_')
				return -1;
			CTowerDefenseElement _tower =buildtower.get(buildtowerID);
			_tower.setPosX(x);
			_tower.setPosY(y);			
			tower.add(_tower);
			gold -= _tower.getCost();
			_tower.setUniqueID(uniqueIDCounter);
			uniqueIDCounter += 1;
			Synchronize();
			return uniqueIDCounter - 1;
		} else
			return -2;
	}

	private void BuffNearByTower(CTDBuffTower _ctdtower) {
		ArrayList<CTDTower> nearbytower = NextAllTower(_ctdtower);
		for (int i = 0; i < nearbytower.size(); i++) {
			nearbytower.get(i).setBuff(_ctdtower.getBuffamount());
		}

	}

	/**
	 * 
	 * @throws InvalidArgument
	 */
	public void RunAction() throws InvalidArgument {
		char[][] _mapelements = inMap.getMapElements();
		for(int i=0;i<creeps.size();i++)
		{			
			creeps.set(i, CreepWalk.Interpolate(creeps.get(i), inMap.getWayPathByID(creeps.get(i).getWayPathID()), _mapelements[creeps.get(i).getPosX()][creeps.get(i).getPosY()]));

			if(inMap.getMapElementValue(creeps.get(i).getPosX(), creeps.get(i).getPosY()) == 'X')
				EndCreeps(creeps.get(i).getUniqueID());
			//if(inMap.getMapElementValue(creeps.get(i).getPosX(), creeps.get(i).getPosY()) == 'X')
				
		}
		for (int i = 0; i < tower.size(); i++)
			CalcDamage(tower.get(i));
	}

	private void AnimDmg(CTDTower elem, CTDCreep _creep, int _dmg)
			throws InvalidArgument {
		animationofdamage.addElement(_creep, elem, _dmg);
	}

	private void CalcDamage(CTowerDefenseElement elem) throws InvalidArgument {
		if (elem instanceof CTDBuffTower) {
			if (((CTDBuffTower) elem).checkAttrate() == true) {
				BuffNearByTower((CTDBuffTower) elem);
				((CTDBuffTower) elem).updateAttrate();
			}
			return;
		}
		//System.out.println("hallo1");
		//if (((CTDTower) elem).checkAttrate() == true)
			//return;
		if (false) {
			System.out.println("hallo2");
			ArrayList<CTDCreep> _creeps = NextAllTargets((CTDTower) elem);
			if (_creeps.size() < 1)
				return;
			for (int i = 0; i < _creeps.size(); i++) {
				_creeps.get(i).flushSlow();
				ArrayList<CTDImmunity> immune = _creeps.get(i).getImmunity();
				boolean cont = false;
				if (immune.size() < 1)
					for (int j = 0; j < immune.size(); j++) {
						if (((CTDAoET) elem).checkImmunity(immune.get(j)) == true) {
							cont = true;
							break;
						}
					}
				if (cont == true)
					continue;

				if (((CTDAoET) elem).getDmgtype() == "slow") {
					float walkspeed = ((CTDAoET) elem).getDamage();
					_creeps.get(i).setSlowSpeed(walkspeed);
					return;
				}
				if (((CTDAoET) elem).getDmgtype() == "splash") {
					//BEARBEITEN
				}
				int dmgbytower = ((CTDAoET) elem).getDamage();
				int life = _creeps.get(i).getLP();
				int _lp = life - dmgbytower;
				if (_lp <= 0)
					CreepDead(_creeps.get(i));
				AnimDmg((CTDTower) elem, _creeps.get(i), dmgbytower);
				gold += waves.get(_creeps.get(i).getWayPathID())
						.getGoldPerKill();
				_creeps.get(i).setLP(_lp);
			}
			((CTDAoET) elem).updateAttrate();
			return;
		}
		//System.out.println("hallo2");
		CTDCreep victim = NextSingleTarget((CTDTower) elem);
		if(victim == null) return;
		System.out.println("Found Victim");
		ArrayList<CTDImmunity> immune = victim.getImmunity();
		boolean cont = false;
		if (immune.size() < 1)
			for (int j = 0; j < immune.size(); j++) 
			{
				if (((CTDAoET) elem).checkImmunity(immune.get(j)) == true) {
					cont = true;
					break;
				}
			}
		if (cont == true)
			return;
		int dmgbytower = ((CTDTower) elem).getDamage();
		int life = victim.getLP();
		int _lp = life - dmgbytower;
		if (_lp <= 0)
			CreepDead(victim);
		AnimDmg((CTDTower) elem, victim, dmgbytower);
		gold += waves.get(victim.getWayPathID()).getGoldPerKill();
		((CTDTower) elem).updateAttrate();
		victim.setLP(_lp);

	}

	private void CreepDead(CTDCreep ctdCreep) {
		creeps.remove(ctdCreep);
		deadcreeps.add(ctdCreep);

	}

	public int[] pickUpCreeps() {
		int[] uniqueIDs = new int[creeps.size()];
		for (int i = 0; i < creeps.size(); i++) {
			uniqueIDs[i] = creeps.get(i).getUniqueID();
		}
		return uniqueIDs;
	}

	public ArrayList<CTDCreep> pickUpDeadCreeps() {
		ArrayList<CTDCreep> temp = new ArrayList<CTDCreep>();
		temp = (ArrayList<CTDCreep>) deadcreeps.clone();
		deadcreeps.clear();
		return temp;
	}

	private CTDCreep NextSingleTarget(CTDTower _tower) {
		for (int i = 0; i < creeps.size(); i++) {
			double pointX = creeps.get(i).getAbsoluteX() - (_tower.getPosX() * props.FieldInPixels);
			if(pointX<0)
				pointX *= -1;
			System.out.println(pointX);
			double pointY = creeps.get(i).getAbsoluteY() - (_tower.getPosY() * props.FieldInPixels);
			if(pointY<0)
				pointY *= -1;	
			System.out.println(pointY);					
			double range = _tower.getAttrange();
			System.out.println(range);	
			if (pointX <= range || pointY <= range)
				return creeps.get(i);
		}
		return null;
	}

	private ArrayList<CTDCreep> NextAllTargets(CTDTower _tower) {
		ArrayList<CTDCreep> _creeps = new ArrayList<CTDCreep>();
		for (int i = 0; i < creeps.size(); i++) {
			double pointX = creeps.get(i).getAbsoluteX() - (_tower.getPosX() * props.FieldInPixels);
			if(pointX<0)
				pointX *= -1;
			double pointY = creeps.get(i).getAbsoluteY() - (_tower.getPosY() * props.FieldInPixels);
			if(pointY<0)
				pointY *= -1;	
					
			double range = _tower.getAttrange();
			if (pointX <= range && pointY <= range)
				_creeps.add(creeps.get(i));
		}
		return _creeps;
	}

	private ArrayList<CTDTower> NextAllTower(CTDBuffTower _tower) {
		ArrayList<CTDTower> nearbytower = new ArrayList<CTDTower>();
		for (int i = 0; i < tower.size(); i++) {
			double pointX = (tower.get(i).getPosX()* props.FieldInPixels) - (_tower.getPosX()* props.FieldInPixels);
			double pointY = (tower.get(i).getPosY()* props.FieldInPixels) - (_tower.getPosY()* props.FieldInPixels);
			double range = _tower.getBuffrange();
			if (pointX <= range && pointY <= range)
				nearbytower.add(nearbytower.get(i));
		}
		return nearbytower;
	}

	public void EndCreeps(int uniqueID) {		
		points -= 1;
		removeCreepByID(uniqueID);
	}

	private void removeCreepByID(int id) {
		for (int i = 0; i < creeps.size(); i++)
			if (creeps.get(i).getUniqueID() == id)
				creeps.remove(i);
	}

	public boolean CheckIfBuildAllowed(int PosX, int PosY) throws InvalidArgument {
		if (inMap.checkExTowerOnPlace(PosX, PosY) == false
				|| inMap.checkExTowerOnPlace(PosX, PosY) == false
				|| inMap.checkPathOnPlace(PosX, PosY) == false)
			return false;
		return true;
	}
	public void reset()
	{
		creeps.clear();
		tower.clear();
		if(inMap != null){
		inMap.flushMapElements();
		inMap.flushAllTower();}
		waves.clear();
		points = constants.startPoints;
		gold = constants.startGold;
	}
	public char[][] getLevel()
	{
		if(inMap == null)
			return null;
		return inMap.getMapElements();
	}
	public char getSingleMapElement(int x, int y) throws InvalidArgument
	{
		return inMap.getMapElementValue(x, y);
	}
	public boolean checkTowerOnPosition(int x, int y) throws InvalidArgument
	{
		return inMap.checkTowerOnPlace(x, y);
	}
	public void CreateCreepOnPlace(int x, int y) throws InvalidArgument
	{
		if (inMap.getMapElementValue(x,y) == '#'
				|| inMap.getMapElementValue(x, y) != 'S')
			return;
		CTDCreep _creep = new CTDCreep();
		_creep.setPosX(x);
		_creep.setPosY(y);
		_creep.setLP(1);
		_creep.setCost(0);
		_creep.setWalkSpeed(24);
		_creep.setAbsoluteX(x*props.FieldInPixels);
		_creep.setAbsoluteY(y*props.FieldInPixels);
		_creep.setUniqueID(uniqueIDCounter);
		uniqueIDCounter +=1;
		_creep.setWayPathID(0);
		creeps.add(_creep);
		
	}
	public boolean CheckCreepOnPlace(int x,int y)
	{
		for(int i =0; i<creeps.size();i++)
		{
			if(creeps.get(i).getPosX() == x && 
					creeps.get(i).getPosY() == y)
				return true;
		}	
		return false;
	}
	public int getCreepsOnPlace(int x, int y)
	{
		int count = 0;
		for(int i =0; i<creeps.size();i++)
		{
			if(creeps.get(i).getPosX() == x && 
					creeps.get(i).getPosY() == y)
				count += 1;
		}	
		return count;
	}
	public void setSingleTowerElement(int x, int y) throws InvalidArgument
	{		
		CTDTower _tower = CreateDefaultTower(x,y);
		tower.add(_tower);
		inMap.addNewMapElement(_tower);
	}
	public CTDTower CreateDefaultTower(int x, int y)
	{
		CTDTower _tower = new CTDTower();
		_tower.setAttrange(30);
		_tower.setAttrate(100);
		_tower.setDamage(100);
		_tower.setLevel(1);
		_tower.setName("TestTower");
		_tower.setUniqueID(uniqueIDCounter);
		uniqueIDCounter+=1;
		_tower.setCost(100);
		_tower.setPosX(x);
		_tower.setPosY(y);
		_tower.setUpgradeCost(1000);
		return _tower;
	}
	public int BuyTower(CTowerDefenseElement _tower) throws InvalidArgument {

		if (CheckGold(_tower.getCost()) == true) {
			if (inMap.checkExTowerOnPlace(_tower.getPosX(), _tower.getPosY()) != false
					&& inMap.checkFreePlace(_tower.getPosX(), _tower.getPosY()) != true)
				return -1;
			_tower.setUniqueID(uniqueIDCounter);
			uniqueIDCounter+=1;
			tower.add(_tower);
			gold -= _tower.getCost();
			Synchronize();
			return uniqueIDCounter - 1;
		} else
			return -2;
	}
	public int SellTower(int x, int y) throws InvalidArgument
	{
		
			for (int i = 0; i < tower.size(); i++)
				if (tower.get(i).getPosX() == x && 
						tower.get(i).getPosY() == y) {
					int money = tower.get(i).getCost();
					double _gold = money * props.resellPercentage / 100;
					gold += _gold;
					tower.remove(i);
					Synchronize();
				}
			return 0;
	}
	public boolean UpgradeTower(int x, int y) throws InvalidArgument {
		for (int i = 0; i < tower.size(); i++)
			if (tower.get(i).getPosX() == x
					&& tower.get(i).getPosY() == y) {
				if (CheckGold(((CTDTower) tower.get(i)).getUpgradeCost()) == true) {
					((CTDTower) tower.get(i)).Upgrade();
					gold -= ((CTDTower) tower.get(i)).getUpgradeCost();
					Synchronize();
					return true;
				} else
					return false;

			}
		return false;
	}


}
